using System;
using System.Collections;
using System.Collections.Generic;

namespace fuzzyKittenVNC
{
	
	namespace VNCMessages
	{
		public abstract class VNCServerMessage : VNCMessage{
			public bool isPartial = true;
			public enum msgTypes {
				SERVERINIT = 0xff,
				FRAMEBUFFERUPDATE = 0
			};
			
			public msgTypes type;
			public uint bytesMissing = 0;
			public static VNCServerMessage createFromStream(byte[] stream, VNCClient client) {
				switch((int) stream[0]) {
					case 0:
						return FrameBufferUpdate.createFromStream(stream,client);
					default :
						return null;
				}				
			}
			public abstract byte[] getMessage();
			
			public abstract bool updateContent(byte[] stream, int received);
		};
		

				
		public class ServerInit : VNCServerMessage {
			public int frameBufferWidth = 0;
			public int frameBufferHeight = 0;
			public bool valid = false;
			public msgTypes type = VNCServerMessage.msgTypes.SERVERINIT; 
			public struct PixelFormat {
				public int bits_per_pixel;
				public int depth;
				public bool bigEndianFlag;
				public bool trueColorFlag;
				public int redMax;
				public int greenMax;
				public int blueMax;
				public int redShift;
				public int greenShift;
				public int blueShift;
			}
			public string name;
			
			public PixelFormat pixFormat;
			
			public override bool updateContent(byte[] stream, int received)	{
				return true; // ignore
			}
			
			public new static ServerInit createFromStream(byte[] stream,VNCClient client) {
				
				ServerInit msg = new ServerInit();
				byte[] u16 = new byte[2];
				byte[] u32 = new byte[4];
				
				Array.Copy(stream,u16,2);
				msg.frameBufferWidth = VNCClient.bytesToInt(u16);
				Array.Copy(stream,2,u16,0,2);
				msg.frameBufferHeight = VNCClient.bytesToInt(u16);
				
				msg.pixFormat = new fuzzyKittenVNC.VNCMessages.ServerInit.PixelFormat();
				
				msg.pixFormat.bits_per_pixel = stream[4];
				msg.pixFormat.depth = stream[5];
				msg.pixFormat.bigEndianFlag = stream[6] == 1;
				msg.pixFormat.trueColorFlag = stream[7] == 1;
				
				Array.Copy(stream,8,u16,0,2);
				msg.pixFormat.redMax = VNCClient.bytesToInt(u16,true);	
				Array.Copy(stream,10,u16,0,2);
				msg.pixFormat.greenMax = VNCClient.bytesToInt(u16,true);
				Array.Copy(stream,12,u16,0,2);
				msg.pixFormat.blueMax = VNCClient.bytesToInt(u16,true);
				
				msg.pixFormat.redShift = stream[14];
				msg.pixFormat.greenShift = stream[15];	
				msg.pixFormat.blueShift = stream[16];
			
				Array.Copy(stream,20,u32,0,4);
				int stringLength = VNCClient.bytesToInt(u32);
				byte[] name = new byte[stringLength];
				Array.Copy(stream,24,name,0,stringLength);
				msg.name = System.Text.Encoding.ASCII.GetString(name);

				if(!msg.isValid())
					throw new InvalidServerInitException();
				
				return msg;
			}
			
			public void validate() {
				bool valid = true;
				if(this.frameBufferHeight == 0  || this.frameBufferWidth == 0) 
					valid = false;

				if(this.pixFormat.depth > this.pixFormat.bits_per_pixel)
					valid = false;
				
				if(this.pixFormat.bits_per_pixel>32 || this.pixFormat.bits_per_pixel % 8 != 0)
					valid = false;
				this.valid = valid;
			}
			
			public bool isValid() {
				this.validate();
				return this.valid;
			}
			
			public override byte[] getMessage() {
				return null;
			}
		}
		
		public class FrameBufferUpdate : VNCMessages.VNCServerMessage {
			public VNCClientFramebuffer fb;
			public int byteOffset = 0;
			public int nrOfRects;
			public byte[] rawData = new byte[0];
			public new msgTypes type = VNCServerMessage.msgTypes.FRAMEBUFFERUPDATE; 

			public class Rectangle {
				public uint x;
				public uint y;
				public uint width;
				public uint height;
				public AbstractRFBEncoding encoding;

				public bool finished = false;
			}
			public Rectangle[] rectangles;
			
			public new static FrameBufferUpdate createFromStream(byte[] stream,VNCClient client) {
				FrameBufferUpdate msg = new FrameBufferUpdate();
				VNCClientFramebuffer fb = VNCClientFramebuffer.getInstanceForClient(client);
				msg.fb = fb; 
				return msg;
			}
			
			public override bool updateContent(byte[] stream, int received) {

				byte[] swap = this.rawData;
				this.rawData = new byte[received+this.rawData.Length];
				Array.Copy(stream,0,this.rawData,swap.Length,received);
				Array.Copy(swap,0,this.rawData,0,swap.Length);
				
				if(this.rawData.Length < this.byteOffset+this.bytesMissing)
					return false;

				if(received<5) {
					this.bytesMissing = 5;
					return false; 
				}
			
				if(byteOffset == 0) {
					nrOfRects = ((int) this.rawData[2]<<8 | (int) this.rawData[3]);
					rectangles = new Rectangle[nrOfRects];
					byteOffset = 4;
				}
				
				uint requiredBytesForRectangle = 0;
				for(int i=0;i<nrOfRects;i++) {
					if(rectangles[i] != null && rectangles[i].finished)
						continue;
					
					if(this.rawData.Length < byteOffset + 12) {
						this.bytesMissing = 12;
						return false;
					} 
					rectangles[i] = new Rectangle();
					rectangles[i].x = (uint) this.rawData[byteOffset] << 8| (uint) this.rawData[byteOffset+1];
					rectangles[i].y = (uint) this.rawData[byteOffset+2] << 8 | (uint)this.rawData[byteOffset+3];
					rectangles[i].width = (uint) this.rawData[byteOffset+4] << 8 | (uint) this.rawData[byteOffset+5];
					rectangles[i].height = (uint) this.rawData[byteOffset+6] << 8 | (uint) this.rawData[byteOffset+7];
					
					requiredBytesForRectangle = RFBEncoding.getEncodingSize(
						rectangles[i].height,rectangles[i].width,
						fb.format.pixFormat.bits_per_pixel,byteOffset+8,this.rawData
					);
					if(this.rawData.Length < requiredBytesForRectangle+byteOffset) {
						this.bytesMissing = requiredBytesForRectangle;
						return false;
					}
					rectangles[i].encoding = 
						RFBEncoding.getEncoding(
							rectangles[i].height,rectangles[i].width,
							fb.format.pixFormat.bits_per_pixel,byteOffset+8,this.rawData);
					rectangles[i].finished = true;
					byteOffset += 12+ (int) requiredBytesForRectangle;
					Logger.log(String.Format("Received rectangle: x/y : {0}/{1} w/h {2}/{3} bytes",rectangles[i].x,rectangles[i].y,rectangles[i].width,rectangles[i].height),Logger.LEVEL.DEBUG);
				}
				
				this.bytesMissing = 0;
				Logger.log(String.Format("New FramebufferUpdate \\w {0} bytes",this.rawData.Length),Logger.LEVEL.DEBUG);
				this.rawData = null; // cleanup
				GC.Collect();
				return true;
			}
			
			public void updateFramebufer() {
				foreach(Rectangle rec in this.rectangles) {
					if(rec.encoding != null)
						fb.updateArea(rec.x,rec.y,rec.width,rec.height,rec.encoding.pixels);
				
				} 
			}
			
			public override byte[] getMessage() {
				return null;
			}			
		}
	}
}

